package com.meilingcloud.biobank.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.base.Joiner;
import com.meilingcloud.biobank.domain.Container;
import com.meilingcloud.biobank.domain.ContainerType;
import com.meilingcloud.biobank.domain.SampleBackup;
import com.meilingcloud.biobank.domain.Tube;
import com.meilingcloud.biobank.domain.dto.ContainerBatchDto;
import com.meilingcloud.biobank.domain.vo.StatisticVo;
import com.meilingcloud.biobank.mapper.ContainerMapper;
import com.meilingcloud.biobank.service.IContainerService;
import com.meilingcloud.biobank.service.IContainerTypeService;
import com.meilingcloud.biobank.service.ISampleBackupService;
import com.meilingcloud.biobank.service.ITubeService;
import com.meilingcloud.common.constant.Constants;
import com.meilingcloud.common.enums.TubeStatus;
import com.meilingcloud.common.exception.CustomException;
import com.meilingcloud.common.utils.SecurityUtils;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 容器 Service业务层处理
 *
 * @author chenl
 * @date 2021-01-25
 */
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Service
public class ContainerServiceImpl extends ServiceImpl<ContainerMapper, Container> implements IContainerService {

    private final IContainerTypeService containerTypeService;
    private final ITubeService tubeService;
    private final ISampleBackupService sampleBackupService;

    @Override
    public List<Container> queryList(Container container) {
        LambdaQueryWrapper<Container> lqw = Wrappers.lambdaQuery();
        if (StringUtils.isNotBlank(container.getContainerName())) {
            lqw.like(Container::getContainerName, container.getContainerName());
        }
        return this.list(lqw);
    }

    @Cacheable(value = Constants.CONTAINER_KEY, key = "#parentId", unless = "#result == null ", cacheManager = "cacheManager1d")
    @Override
    public List<Container> lazyTree(Long parentId) {
        List<Container> containers = this.list(new LambdaQueryWrapper<Container>().eq(Container::getParentId, parentId).orderByAsc(Container::getContainerSort));
        List<Container> list = containers.stream().map(item -> {
            ContainerType containerType = containerTypeService.queryById(item.getContainerTypeId());
            item.setContainerTypeCode(containerType.getContainerTypeCode());
            item.setContainerTypeName(containerType.getContainerTypeName());
            item.setContainerLevel(containerType.getContainerTypeLevel());
            item.setContainerIcon(containerType.getContainerTypeIcon());
            ContainerType parentContainerType = containerTypeService.queryById(containerType.getParentId());
            item.setParentContainerTypeCode(parentContainerType.getContainerTypeCode());
            String name = StringUtils.isNotEmpty(item.getContainerName()) ? ("_" + item.getContainerName()) : "";
            String title = item.getContainerTypeName() + "_" + item.getContainerSort() + name + " ";
            item.setTitle(title);
            item.setStorageRate(item.getTotalCount() == 0 ? 0 :
                    BigDecimal.valueOf((double) item.getCurrentCount() / item.getTotalCount() * 100).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());
            return item;
        }).collect(Collectors.toList());
        return list;
    }

    @Override
    public List<Container> getAllParentsByChild(Long containerId) {
        List<Container> list = baseMapper.getAllParentsByChild(containerId);
        return list;
    }

    @CacheEvict(value = Constants.CONTAINER_KEY, allEntries = true)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveContainer(Container container) {
        Container parentContainer = this.getById(container.getParentId());
        ContainerType containerType = containerTypeService.queryById(container.getContainerTypeId());
        String containerLocation = "";
        if (ObjectUtil.isNotEmpty(parentContainer)) {
            containerLocation += parentContainer.getContainerLocation();
        }
        containerLocation += "/" + containerType.getContainerTypeName() + "_" + container.getContainerSort() +
                (StringUtils.isNotBlank(container.getContainerName()) ? "_" + container.getContainerName() : "");
        container.setContainerLocation(containerLocation);
        if (CollectionUtil.isNotEmpty(container.getSampleTypes())) {
            container.setContainerSampleType(Joiner.on(",").join(container.getSampleTypes()));
        }
        boolean result = this.save(container);

        List<Container> allParentsByChild = getAllParentsByChild(container.getContainerId());
        if (ObjectUtil.isNotEmpty(container.getBoxRow()) && ObjectUtil.isNotEmpty(container.getBoxColumn())) {
            int row = container.getBoxRow();
            int column = container.getBoxColumn();
            if (row > 20) {
                throw new CustomException("盒子行数不能超过20");
            }
            // 向父容器更新存储总数
            int boxTotalCount = row * column;
            List<Container> collect = allParentsByChild.stream().map(item -> {
                item.setTotalCount(item.getTotalCount() + boxTotalCount);
                return item;
            }).collect(Collectors.toList());
            this.updateBatchById(collect);

            // 是否交叉布局
            boolean crossLayout = "1".equals(container.getBoxLayout());
            // 向冻存管表写入数据
            List<Tube> tubeList = new ArrayList<>();
            int num = 0;
            for (int i = 0; i < row; i++) {
                for (int j = 0; j < column; j++) {
                    num++;
                    String location = Constants.TUBE_ROW[i] + (j + 1);
                    Tube tube = new Tube();
                    tube.setContainerId(container.getContainerId());
                    tube.setTubeLocation(location);
                    tube.setTubeSort(num);
                    tube.setCreateBy(SecurityUtils.getUsername());
                    if (crossLayout && num % 2 == 1) {
                        tube.setTubeState(TubeStatus.FORBIDDEN.getCode()); // 禁用
                    }
                    tubeList.add(tube);
                }
            }
            tubeService.saveBatch(tubeList);
        }
        return result;
    }

    @CacheEvict(value = Constants.CONTAINER_KEY, allEntries = true)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean batchAddition(ContainerBatchDto dto) {
        // 添加冻存盒到subarea列表 用于下方递归方法批量插入
        ContainerBatchDto.Subarea box = new ContainerBatchDto.Subarea();
        ContainerType boxType = containerTypeService.queryByTypeCode("box");
        if (ObjectUtil.isNotEmpty(boxType)) {
            box.setContainerTypeId(boxType.getContainerTypeId());
        }
        box.setCount(dto.getBoxCount());
        dto.getSubareas().add(box);
        // 计算孔位总数
        int total = dto.getBoxColumn() * dto.getBoxRow();
        List<ContainerBatchDto.Subarea> subareaList = dto.getSubareas();
        for (ContainerBatchDto.Subarea subarea : subareaList) {
            total *= subarea.getCount();
        }
        ContainerBatchDto.Subarea subarea = null;
        for (int i = subareaList.size() - 1; i >= 0; i--) {
            if (subarea != null) {
                subareaList.get(i).setChild(subarea);
            }
            subarea = subareaList.get(i);
        }
        // 更新父容器存储总数
        Container parentContainer = this.getById(dto.getParentId());
        parentContainer.setTotalCount(total);
        boolean result = this.updateById(parentContainer);
        // 更新父容器的父容器的存储总数 -- 批量添加功能只对冰箱级的容器 所以此处只更新冰箱级容器的父容器即可
        Container ppContainer = this.getById(parentContainer.getParentId());
        ppContainer.setTotalCount(ppContainer.getTotalCount() + parentContainer.getTotalCount());
        this.updateById(ppContainer);
        // 插入容器分区
        handleBatchSubarea(total, dto.getParentId(), parentContainer.getContainerLocation(), subarea, dto.getBoxRow(), dto.getBoxColumn(), dto.getBoxLayout());
        return result;
    }

    /**
     * 递归插入容器分区
     *
     * @param total     存储总数
     * @param parentId  父级id
     * @param location  格式化位置
     * @param subarea   分区
     * @param row       行
     * @param column    列
     * @param boxLayout 布局
     */
    @Transactional(rollbackFor = Exception.class)
    public void handleBatchSubarea(int total, Long parentId, String location, ContainerBatchDto.Subarea subarea, int row, int column, String boxLayout) {
        ContainerType containerType = containerTypeService.getById(subarea.getContainerTypeId());
        total = total / subarea.getCount();
        location += "/" + containerType.getContainerTypeName() + "_";

        for (int j = 0; j < subarea.getCount(); j++) {
            Container container = new Container();
            container.setParentId(parentId);
            container.setContainerTypeId(containerType.getContainerTypeId());
            container.setTotalCount(total);
            container.setContainerSort(j + 1);
            container.setContainerLocation(location + (j + 1));
            container.setCreateBy(SecurityUtils.getUsername());
            if (subarea.getChild() == null) {
                container.setBoxRow(row);
                container.setBoxColumn(column);
                container.setBoxLayout(boxLayout);
            }
            this.save(container);
            if (subarea.getChild() != null) {
                handleBatchSubarea(total, container.getContainerId(), location + (j + 1), subarea.getChild(), row, column, boxLayout);
            } else {
                handleBatchTube(boxLayout, row, column, container.getContainerId());
            }
        }
    }

    /**
     * 插入冻存盒
     *
     * @param boxLayout   布局
     * @param row         行
     * @param column      列
     * @param containerId 关联容器id
     */
    @Transactional(rollbackFor = Exception.class)
    public void handleBatchTube(String boxLayout, int row, int column, Long containerId) {
        // 是否交叉布局
        boolean crossLayout = "1".equals(boxLayout);
        // 向冻存管表写入数据
        List<Tube> tubeList = new ArrayList<>();
        int num = 0;
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < column; j++) {
                num++;
                String location = Constants.TUBE_ROW[i] + (j + 1);
                Tube tube = new Tube();
                tube.setContainerId(containerId);
                tube.setTubeLocation(location);
                tube.setTubeSort(num);
                tube.setCreateBy(SecurityUtils.getUsername());
                if (crossLayout && num % 2 == 1) {
                    tube.setTubeState(TubeStatus.FORBIDDEN.getCode()); // 禁用
                }
                tubeList.add(tube);
            }
        }
        tubeService.saveBatch(tubeList);
    }

    @CacheEvict(value = Constants.CONTAINER_KEY, allEntries = true)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean updateContainer(Container container) {
        Container oldContainer = this.getById(container.getContainerId());
        ContainerType containerType = containerTypeService.queryById(container.getContainerTypeId());
        if (ObjectUtil.notEqual(oldContainer.getBoxRow(), container.getBoxRow()) || ObjectUtil.notEqual(oldContainer.getBoxColumn(), container.getBoxColumn())) {
            throw new CustomException("不允许更改盒行数或盒列数");
        }
        List<Container> containers = new ArrayList<>();
        List<SampleBackup> sampleBackupList = new ArrayList<>();
        // 如果修改了容器名称
        if (!StringUtils.equals(oldContainer.getContainerName(), container.getContainerName()) ||
                ObjectUtil.notEqual(oldContainer.getContainerSort(), container.getContainerSort())) {
            // 修改容器格式化位置
            String oldLocation = oldContainer.getContainerLocation();
            if (oldLocation.contains("/")) {
                String[] locSplit = oldLocation.substring(1, oldLocation.length() - 1).split("/");
                if (locSplit.length > 0) {
                    StringBuffer stringBuffer = new StringBuffer();
                    for (int i = 0; i < locSplit.length - 1; i++) {
                        stringBuffer.append("/");
                        stringBuffer.append(locSplit[i]);
                    }
                    stringBuffer.append("/");
                    stringBuffer.append(containerType.getContainerTypeName() + "_" + container.getContainerSort() +
                            (StringUtils.isNotBlank(container.getContainerName()) ? "_" + container.getContainerName() : ""));
                    container.setContainerLocation(stringBuffer.toString());
                }
            }
            // 修改所有子容器的格式化位置
            containers = baseMapper.getAllChildrenByParent(container.getContainerId());
            if (CollectionUtil.isNotEmpty(containers)) {
                containers.stream().map(item -> {
                    String newLocation = item.getContainerLocation().replace(oldLocation, container.getContainerLocation());
                    item.setContainerLocation(newLocation);

                    // 修改所有子容器下样本的格式化位置
                    if (ObjectUtil.isNotEmpty(item.getBoxRow()) && ObjectUtil.isNotEmpty(item.getBoxColumn())) {
                        List<Tube> tubes = tubeService.list(new LambdaQueryWrapper<Tube>().eq(Tube::getContainerId, item.getContainerId())
                                .and(wrapper -> wrapper.isNotNull(Tube::getSampBackupId).gt(Tube::getSampBackupId, 0L)));
                        List<Long> sampBackupIds = tubes.stream().map(Tube::getSampBackupId).collect(Collectors.toList());
                        if (CollectionUtil.isNotEmpty(sampBackupIds)) {
                            List<SampleBackup> sampleBackups = sampleBackupService.listByIds(sampBackupIds);
                            List<SampleBackup> samples = sampleBackups.stream().map(samp ->
                                    tubes.stream().filter(t -> ObjectUtil.equal(samp.getSampBackupId(), t.getSampBackupId()))
                                            .map(tube -> {
                                                samp.setSampLocation(item.getContainerLocation() + "/" + tube.getTubeLocation());
                                                return samp;
                                            }).findAny().orElse(null)
                            ).collect(Collectors.toList());
                            sampleBackupList.addAll(samples);
                        }
                    }
                    return item;
                }).collect(Collectors.toList());
            }
        }
        if (CollectionUtil.isNotEmpty(container.getSampleTypes())) {
            container.setContainerSampleType(Joiner.on(",").join(container.getSampleTypes()));
        }
        containers.add(container);
        if (CollectionUtil.isNotEmpty(sampleBackupList)) {
            sampleBackupService.updateBatchById(sampleBackupList);
        }
        return this.updateBatchById(containers);
    }

    @CacheEvict(value = Constants.CONTAINER_KEY, allEntries = true)
    @Override
    public boolean updateContainerBatch(List<Container> containers) {
        return this.updateBatchById(containers);
    }

    @CacheEvict(value = Constants.CONTAINER_KEY, allEntries = true)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean removeContainer(Long containerId) {
        Container container = this.getById(containerId);

        // 检查该容器下是否有样本
        if (container.getCurrentCount() > 0) {
            throw new CustomException("该容器下存在样本，不允许删除");
        }

        List<Container> childrenContainers = this.list(new LambdaQueryWrapper<Container>().eq(Container::getParentId, containerId));
        if (CollectionUtil.isNotEmpty(childrenContainers)) {
            throw new CustomException("该容器下存在子容器，不允许删除");
        }

        if (ObjectUtil.isNotNull(container) && ObjectUtil.isNotEmpty(container.getBoxRow())
                && ObjectUtil.isNotEmpty(container.getBoxColumn())) {
            // 删除冻存盒表
            List<Tube> tubes = tubeService.list(new LambdaQueryWrapper<Tube>().eq(Tube::getContainerId, containerId));
            List<Long> tubeIds = tubes.stream().map(Tube::getTubeId).collect(Collectors.toList());
            if (CollectionUtil.isNotEmpty(tubeIds)) {
                tubeService.removeByIds(tubeIds);
            }
            // 将父容器的存储数量减掉
            int boxTotalCount = container.getBoxColumn() * container.getBoxRow();
            List<Container> allParentsByChild = getAllParentsByChild(container.getContainerId());
            List<Container> collect = allParentsByChild.stream().filter(item -> !item.getContainerId().equals(containerId)).map(item -> {
                item.setTotalCount(item.getTotalCount() - boxTotalCount);
                return item;
            }).collect(Collectors.toList());
            if (CollectionUtil.isNotEmpty(collect)) {
                this.updateBatchById(collect);
            }
        }
        // 删除容器
        return this.removeById(containerId);
    }

    @Override
    public List<StatisticVo> statisticContainer() {
        List<Container> list = this.list(new LambdaQueryWrapper<Container>().eq(Container::getParentId, 0));
        List<Container> containerList = new ArrayList<>();
        list.forEach(item -> {
            List<Container> containers = this.list(new LambdaQueryWrapper<Container>().eq(Container::getParentId, item.getContainerId()));
            containerList.addAll(containers);
        });
        List<StatisticVo> collect = containerList.stream().map(item -> {
            StatisticVo statisticVo = new StatisticVo();
            statisticVo.setName((StringUtils.isNotBlank(item.getContainerName()) ? item.getContainerName() + "_" : "") + item.getContainerSort());
            statisticVo.setValue(item.getCurrentCount());
            return statisticVo;
        }).collect(Collectors.toList());
        return collect;
    }

    @Override
    public List<Container> getLeafByParent(Long containerId) {
        return baseMapper.getAllLeafByParent(containerId);
    }

    @CacheEvict(value = Constants.CONTAINER_KEY, allEntries = true)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean copyContainer(Container container) {
        // 复制容器下所有子容器以及冻存盒 排序为同层级最大值+1 只修改最外层排序、名称及描述
        String oldLocation = container.getContainerLocation();
        String newLocation = "";
        String userName = SecurityUtils.getUsername();
        Date currentDate = new Date();

        // 向父容器更新存储总数
        int boxTotalCount = container.getTotalCount();
        List<Container> allParentsByChild = getAllParentsByChild(container.getContainerId());
        if (CollectionUtil.isNotEmpty(allParentsByChild)) {
            List<Container> collect = allParentsByChild.stream().map(item -> {
                if (ObjectUtil.equal(item.getContainerId(), container.getContainerId())) {
                    return null;
                }
                item.setTotalCount(item.getTotalCount() + boxTotalCount);
                return item;
            }).filter(Objects::nonNull).collect(Collectors.toList());
            this.updateBatchById(collect);
        }

        // 1. 获取最外层同层级排序最大值
        Container maxSortContainer = this.getOne(new LambdaQueryWrapper<Container>().eq(Container::getParentId, container.getParentId())
                .orderByDesc(Container::getContainerSort).last("limit 1"));
        container.setContainerSort(maxSortContainer.getContainerSort() + 1);

        // 2. 最外层容器 修改容器格式化位置
        if (oldLocation.contains("_")) {
            String tmpLocation = StringUtils.isNotBlank(container.getContainerName()) ? oldLocation.substring(0, oldLocation.lastIndexOf("_"))
                    : oldLocation;
            if (tmpLocation.contains("_")) {
                newLocation = tmpLocation.substring(0, tmpLocation.lastIndexOf("_")) + "_" + container.getContainerSort()
                        + (StringUtils.isNotBlank(container.getContainerName()) ? "_" + container.getContainerName() : "");
            } else {
                newLocation = tmpLocation + "_" + container.getContainerSort()
                        + (StringUtils.isNotBlank(container.getContainerName()) ? "_" + container.getContainerName() : "");
            }
            container.setContainerLocation(newLocation);
        }
        // 设置复制容器的存储样本类型
        if (CollectionUtil.isNotEmpty(container.getSampleTypes())) {
            container.setContainerSampleType(Joiner.on(",").join(container.getSampleTypes()));
        }

        copyChildrenByParent(container, oldLocation, newLocation, userName, currentDate);
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    public void copyChildrenByParent(Container container, String oldLocation, String newLocation, String userName, Date currentDate) {
        // 复制盒子容器
        if (ObjectUtil.isNotEmpty(container.getBoxRow()) && ObjectUtil.isNotEmpty(container.getBoxColumn())) {
            List<Tube> tubes = tubeService.list(new LambdaQueryWrapper<Tube>().eq(Tube::getContainerId, container.getContainerId()));
            container.setContainerId(null);
            container.setCurrentCount(0);
            container.setCreateBy(userName);
            container.setCreateTime(currentDate);
            container.setUpdateBy(userName);
            container.setUpdateTime(currentDate);
            container.setBoxBarcode(""); // 盒编码置空
            this.save(container);
            if (CollectionUtil.isNotEmpty(tubes)) {
                List<Tube> tubeList = tubes.stream().map(t -> {
                    t.setTubeId(null);
                    t.setContainerId(container.getContainerId());
                    t.setSampBackupId(0L);
                    return t;
                }).collect(Collectors.toList());
                tubeService.saveBatch(tubeList);
            }
            return;
        }
        // 非盒子容器
        List<Container> containers = this.list(new LambdaQueryWrapper<Container>().eq(Container::getParentId, container.getContainerId()));
        container.setContainerId(null);
        container.setCurrentCount(0);
        container.setCreateBy(userName);
        container.setCreateTime(currentDate);
        container.setUpdateBy(userName);
        container.setUpdateTime(currentDate);
        // 3. 插入子容器
        this.save(container);
        if (CollectionUtil.isNotEmpty(containers)) {
            containers.forEach(item -> {
                item.setParentId(container.getContainerId());
                if (item.getContainerLocation().contains(oldLocation)) {
                    item.setContainerLocation(item.getContainerLocation().replace(oldLocation, newLocation));
                }
//                // 4. 盒类型容器 如果子容器中存在冻存盒 插入冻存盒表
//                if (ObjectUtil.isNotEmpty(item.getBoxRow()) && ObjectUtil.isNotEmpty(item.getBoxColumn())) {
//                    List<Tube> tubes = tubeService.list(new LambdaQueryWrapper<Tube>().eq(Tube::getContainerId, item.getContainerId()));
//                    item.setContainerId(null);
//                    item.setCurrentCount(0);
//                    item.setCreateBy(userName);
//                    item.setCreateTime(currentDate);
//                    item.setUpdateBy(userName);
//                    item.setUpdateTime(currentDate);
//                    item.setBoxBarcode(""); // 盒编码置空
//                    this.save(item);
//                    if (CollectionUtil.isNotEmpty(tubes)) {
//                        List<Tube> tubeList = tubes.stream().map(t -> {
//                            t.setTubeId(null);
//                            t.setContainerId(item.getContainerId());
//                            return t;
//                        }).collect(Collectors.toList());
//                        tubeService.saveBatch(tubeList);
//                    }
//                    return;
//                }
                copyChildrenByParent(item, oldLocation, newLocation, userName, currentDate);
            });
        }
    }

    @CacheEvict(value = Constants.CONTAINER_KEY, allEntries = true)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean calculateSampleCount() {
        // 1. 将所有容器当前存储数量置零
        List<Container> allContainers = this.list(new LambdaQueryWrapper<Container>().ne(Container::getCurrentCount, 0));
        List<Container> zeroContainers = allContainers.stream().map(item -> {
            item.setCurrentCount(0);
            return item;
        }).collect(Collectors.toList());
        this.updateBatchById(zeroContainers);
        // 2. 更新存在样本的容器及父容器当前存储数量
        List<Container> containers = this.baseMapper.getCurrentSampleCount();
        List<Container> updatedContainers = new ArrayList<>();
        containers.forEach(item -> {
            List<Container> parentsByChild = getAllParentsByChild(item.getContainerId());
            List<Container> collect = parentsByChild.stream().map(p -> {
                if (ObjectUtil.equal(item.getContainerId(), p.getContainerId())) {
                    p.setCurrentCount(item.getCurrentCount());
                    return p;
                }
                p.setCurrentCount(p.getCurrentCount() + item.getCurrentCount());
                return p;
            }).collect(Collectors.toList());
            updatedContainers.addAll(collect);
        });
        if (CollectionUtil.isNotEmpty(updatedContainers)) {
            return this.updateBatchById(updatedContainers);
        }
        return true;
    }

    @Override
    public Container getContainerById(Long containerId) {
        Container container = this.getById(containerId);
        if (StringUtils.isNotBlank(container.getContainerSampleType())) {
            container.setSampleTypes(Arrays.asList(container.getContainerSampleType().split(",")));
        }
        return container;
    }

}
